package model;

import java.awt.Dimension;
import java.awt.Point;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import javax.imageio.ImageIO;
import resourceManager.ResourceManager;


/**
 * A representation of one SLogo Turtle 
 * 
 * @author Andrew Patterson
 */
public class Turtle
{
    private ResourceManager resources =  ResourceManager.getInstance();
    
    // Other classes
    private Pixmap myPixmap;
    private AbstractPen myPen;
    
    //Turtle traits
    private Point myLocation;
    private int myHeading;
    private boolean myVisibility;
    private int mySpeed;

    //Turtle Graphics
    private BufferedImage myImage;
    private Dimension myImageSize;
    

    /**
     * Initializes a turtle at the starting location (center of pixmap), with the starting angle
     * and with a default pen
     */
    public Turtle (Pixmap pixmap, Model model)
    {
    	resources.addResourcesFromFile("turtle");
        myPixmap = pixmap;
        myPen = new DashedLine(model);
        
        myLocation = new Point(myPixmap.getSize().width / 2, myPixmap.getSize().height / 2);
        myHeading = resources.getInteger("starting_heading");
        myVisibility = resources.getBoolean("starting_visibility");
        mySpeed = resources.getInteger("animation_speed");

        setTurtleImage(resources.getString("turtle_file_name"));
        myImageSize = new Dimension(resources.getInteger("turtle_image_width"), resources.getInteger("turtle_image_height"));

    }

    /**
     * Moves the turtle in the direction it is facing
     * 
     * @param distance (+/-) to move the turtle
     * @return the distance that the turtle successfully moved
     */
    public int move (int distance)
    {
        Point startingLocation = new Point(myLocation.x, myLocation.y);
        int angle = myHeading;
        double changeY = 1;
        double changeX = 1;
        
        if (distance < 0)
        {
            angle = LogoMath.simplifyAngle(angle + 180);
        }

        // Use trigonometry to calculate change of x and y
        if (angle >=0 && angle < 90)
        {
            changeY *= -1; 
        }
        else if (angle >= 90 && angle < 180)
        {
            angle = 180 - angle;
            changeX *= -1;
            changeY *= -1;
        }
        else if (angle >= 180 && angle < 270)
        {
            angle = angle - 180;
            changeX *= -1;
        }
        else if (angle >= 270 && angle < 360)
        {
            angle = 360 - angle;
        }
        
        changeX = Math.cos(angle) * Math.abs(distance) * changeX;
        changeY = Math.sin(angle) * Math.abs(distance) * changeY;

        Point endingLocation = new Point((int) (myLocation.getX() + changeX), (int) (myLocation.getY() + changeY));
        endingLocation = calculateInBoundsPoint(startingLocation, endingLocation);
        myLocation.setLocation(endingLocation);
        
        myPen.drawLineMovement(myPixmap, mySpeed, startingLocation,(int)(changeX),(int)(changeY));
        return distance;
    }
    
    
    /**
     * 
     * @param startingPoint of turtle
     * @param endingPoint of turtle ignoring boundaries
     * @return final point of turtle taking into account boundaries
     */
    private Point calculateInBoundsPoint(Point startingPoint, Point endingPoint)
    {
        Line2D turtlePath = new Line2D.Double(startingPoint, endingPoint);
        Point returnPoint = endingPoint;
        
        double firstHitWallDistance = Double.MAX_VALUE;
        Collection<Line2D> allBoundaries = myPixmap.getBoundaries();
        
        for(Line2D currentBoundary : allBoundaries)
        {
            if(turtlePath.intersectsLine(currentBoundary))
            {
                Point intersectionPoint = LogoMath.calculateIntersection(turtlePath, currentBoundary);
                double distance = LogoMath.distanceFormula(startingPoint, intersectionPoint);
                if (distance < firstHitWallDistance)
                {
                    firstHitWallDistance = distance;
                    returnPoint = intersectionPoint;
                }
            }
        }
        return returnPoint;
    }
    
    
    /**
     * Turns the turtle while maintaining an angle >= 0 and <= 360
     * 
     * @param degrees to turn the turtle
     * @return the number of degrees that the turtle successfully turned
     */
    public int turn (int degrees)
    {
        myHeading += degrees;
        myHeading = LogoMath.simplifyAngle(myHeading);
        return degrees;
    }

    
    /**
     * Sets the turtle's absolute position
     * 
     * @param new location
     * @return number of pixels moved
     */
    public int setLocation (Point location)
    {
        int returnValue = (int) LogoMath.distanceFormula(location, myLocation);
        myLocation = location;
        return returnValue;
    }


    /**
     * Sets the turtle's absolute heading
     * 
     * @param new heading
     * @return number of degrees moved
     */
    public int setHeading (int heading)
    {
        int returnValue = heading - myHeading;
        if (Math.abs(returnValue) < 180)
        {}
        else if (returnValue > 0)
        {
            returnValue = 360 - (returnValue);
        }
        else
        {

            returnValue = -heading + myHeading - 360;
        }
        myHeading = heading;
        return returnValue;
    }
    
    /**
     * Sets this turtle's image using a file path
     */
    public void setTurtleImage(String path)
    {
        try
        {
            myImage = ImageIO.read(new File(path));
        }
        catch (IOException e)
        {
            throw ModelException.BAD_IMAGE;
        }
    }


    /**
     * Sets if this turtle is visible
     */
    public void setVisibility (boolean visibility)
    {
        myVisibility = visibility;
    }

    
    /**
     * Returns the heading of this turtle
     */
    public int getHeading ()
    {
        return myHeading;
    }

    
    /**
     * Returns if this turtle is visible
     */
    public boolean getVisibility ()
    {
        return myVisibility;
    }
    
    
    /**
     * Returns the pen which this turtle is carrying
     */
    public AbstractPen getPen ()
    {
        return myPen;
    }
    
    
    /**
     * Returns this turtle's location
     */
    public Point getLocation ()
    {
        return myLocation;
    }
    
    
    /**
     * Returns this turtle's graphical image
     */
    public BufferedImage getTurtleImage()
    {
        return myImage;
    }
    
    /**
     * Returns this turtle's graphic size
     */
    public Dimension getTurtleImageSize()
    {
        return myImageSize;
    }
}

